﻿/// <reference path="FXDom.js" />
/// <reference path="Xpcom.js" />
///--------------------------------------------------------------------
/// <file name="Markup.js">
///   Provides facilities to create markup button elements.
/// </file>
///--------------------------------------------------------------------

NAMFox.ns(function() { with (NAMFox) {

this.SelectionRule = {
    /// <summary>Indicates to move the cursor to the end of the close text.</summary>
    MoveCursorToClose: 0,

    /// <summary>
    ///   Indicates to select the text that contains the open text, the existing selected text, and the close text.
    /// </summary>
    SelectNewText: 1
};

this.MarkupLocation = {
    /// <summary>
    ///   Any screen that involves creating or editing a post.
    /// </summary>
    Post: 0,
    
    /// <summary>The delete single or multiple message screens.</summary>
    DeleteMessage: 1,
    
    /// <summary>The NAMFox quick reply.</summary>
    QuickReply: 2,
    
    /// <summary>The NAMFox quick edit.</summary>
    QuickEdit: 3,
    
    /// <summary>The Neoseeker quick PM.</summary>
    QuickPM: 4,
    
    /// <summary>The Neoseeker edit signature text area.</summary>
    EditSignature: 5,
    
    /// <summary>The Neoseeker sign guestbook page.</summary>
    SignGuestbook: 6,
    
    /// <summary>The Neoseeker ban member screen.</summary>
    BanMember: 7,
    
    /// <summary>The Neoseeker administer thread screen.</summary>
    AdministerThread: 8,
    
    /// <summary>The Neoseeker private message screens.</summary>
    PrivateMessage: 9
};

this.Markup = {
    addCustomMessage: function(messageKey, textArea) {
        /// <summary>
        /// Adds a custom message to the specified text area.
        /// </summary>
        /// <param name="messageKey" type="String">
        /// The key of the message to add.
        /// </param>
        /// <param name="textArea" type="TextArea" domElement="true">
        /// The text area to which to add the custom message.
        /// </param>
        
        var message = "";
        if (messageKey === "defaultMessage") {
            message = $.messages.getDefaultMessage().text;
        }
        else {
            message = $.messages.getMessage(messageKey).text;
        }
        
        var marker = "#selected#";
        var index = message.indexOf(marker);
        if (index !== -1) {
            message = {
                open: message.substring(0, index),
                close: message.substr(index + marker.length)
            };
        }
        
        Markup.addText(message, textArea); 
    },
    
    addText: function(text, textArea, selectionRule, selectionTransformer) {
        /// <summary>Adds arbitrary text to the specified text area.</summary>
        /// <param name="text" type="Object">
        ///   The text to add, OR an object containing the two String
        ///   properties "open" and "close" which indicate what text should go
        ///   at the beginning of the currently selected text and what text
        ///   should go at the end of the currently selected text, respectively.
        /// </param>
        /// <param name="textArea" type="TextArea" domElement="true" optional="true">
        ///   (Optional) The text area to which to add the text. Defaults to
        ///   the currently focused element. An error is thrown if no text area
        ///   can be found.
        /// </param>
        /// <param name="selectionRule" type="Number" integer="true" optional="true">
        ///   (Optional) One of the SelectionRule values. Specifies the
        ///   semantics to use for the location of the selection
        ///   after adding text.
        /// </param>
        /// <param name="selectionTransformer" type="Function" optional="true">
        ///    (Optional) A function which transforms the currently
        ///    selected text as part of the add text process.
        /// </param>
        
        if (!text) {
            return;
        }

        var open = "";
        var close = "";
        if (text.constructor === String) {
            open = text;
        }
        else {
            open = text.open || open;
            close = text.close || close;
        }
        
        $.verbose("Adding " + open + "|" + close + " text.");
        
        if (textArea.get) {
            textArea = textArea.get(0);
        }
        
        if (!textArea) {
            textArea = document.commandDispatcher.focusedElement;
            
            if (!textArea || !FXDom.nodeName(textArea, "TEXTAREA")) {
                throw {
                    message: "Could not find a text area to which to add the text. Try clicking in the text area and trying again.",
                    name: "TextAreaNotFoundError",
                    userFriendly: true
                };
            }
        }
        
        // Save the scroll position of the text area so it can be restored after the text is added.
        if (textArea.clientWidth === 0) {
            var oldValue = textArea.value;
            textArea.value = oldValue + open + close;
        }
        else {
            var oldValue = textArea.value;
            var selectionStart = textArea.selectionStart;
            var selectionEnd = textArea.selectionEnd;
            
            var selection = oldValue.substring(selectionStart, selectionEnd);
            
            var plainTextEditor = textArea.QueryInterface($("nsIDOMNSEditableElement")).
                editor.QueryInterface($("nsIPlaintextEditor"));
                
            // First replace the selection, if it changes.
            if (selectionTransformer) {
                var transformedText = selectionTransformer(selection);
                
                if (selection !== transformedText) {
                    plainTextEditor.insertText(transformedText);
                    selection = transformedText;
                }
            }
            
            // Now insert the correct surrounding text.
            textArea.selectionStart = textArea.selectionEnd = selectionStart;
            plainTextEditor.insertText(open);
            
            textArea.selectionStart = textArea.selectionEnd = selectionStart + open.length + selection.length;
            plainTextEditor.insertText(close);
            
            // Default the selectionRule to MoveCursorToClose if it's not specified.
            if (!selectionRule) {
                selectionRule = SelectionRule.MoveCursorToClose;
            }
            
            // We're already at the selection end if the rule is MoveCursorToClose.
            var closeIndex = textArea.selectionEnd;
            switch (selectionRule) {
                case SelectionRule.SelectNewText:
                    textArea.setSelectionRange(selectionStart, closeIndex);
                    break;
            }
            
            textArea.focus();
        }
    },
    
    create: function(textArea, location) {
        /// <summary>
        ///   Creates markup elements according to the arguments passed into
        ///   this method. All arguments passed after the "location" argument
        ///   will be treated as markup button or drop down prototypes. Examples
        ///   include Markup.anchor or Markup.customMessages.
        /// </summary>
        /// <param name="textArea" type="TextArea" domElement="true">
        ///   The text area with which the new markup elements will be affiliated.
        /// </param>
        /// <param name="location" type="MarkupLocation">
        ///   The location to which these markup elements will be added.
        ///   One of the MarkupLocation values.
        /// </param>
        
        if (textArea.get) {
            textArea = textArea.get(0);
        }
        
        if (!textArea.ownerDocument) {
            throw new Error("Text area must be owned by an HTML document before calling create().");
        }
        
        var owner = textArea.ownerDocument;
        
        if ($FX("#namfox-markup-css", owner).length === 0) {
            $FX("head", owner).append(
                '<link id="namfox-markup-css" href="chrome://namfox/skin/markup.css" rel="stylesheet" type="text/css" />'
            );
        }
        
        var markupElements = [];
        
        var elements = arguments;
        
        var i = 2;
        if (elements[2] && elements[2].constructor === Array) {
            elements = elements[2];
            i = 0;
        }
        
        // These functions are used because you can't use anonymous functions
        // in the loop below, because the value of "arg" will change every
        // loop iteration. This means that whenever a button is clicked or an
        // option is selected in a drop down list, then any reference to "arg"
        // will be the last argument in the elements array.
        function createClickFunction(arg, textArea) {
            return function(e) { arg.click(textArea); };
        }
        
        function createMarkupFunction(arg, textArea) {
            return function(e) {
                var markup = arg.getMarkup(textArea.value);
                Markup.addText(markup, textArea, SelectionRule.MoveCursorToClose);
            };
        }
        
        function createSelectFunction(arg, textArea) {
            return function(e) { arg.change(this.options[this.selectedIndex], textArea); };
        }
        
        var markupBranch = location === MarkupLocation.Post ? $.getConfig().markup : {};
        
        var uuid = 0;
        // Iterate over the markup element specifications that were passed.
        for (; i < elements.length; ++i) {
            var arg = elements[i];
            
            // If the markup element is not enabled for the specified location, then skip it.
            // TODO: Select different markup branches based on the location.
            if (markupBranch && arg.enabled && arg.enabled(markupBranch) === false) {
                continue;
            }
            
            switch (arg.elementType) {
                case "button":
                    // Create a input of type button and then assign its click handler
                    // to the click function of the arg (if it exists) or something that adds
                    // the markup manually.
                    var input = $FX("<input>", owner).attr({ className: "namfox-markup-button", name: arg.name, type: "button", value: arg.text });
                    input.click(
                        arg.click && createClickFunction(arg, textArea) ||
                        createMarkupFunction(arg, textArea)
                    );
                    markupElements.push(input.get(0));
                    break;
                    
                case "select":
                    var select = $FX("<select>", owner).attr("name", arg.name);
                    arg.fill(select);
                    select.change(createSelectFunction(arg, textArea));
                    markupElements.push(select.get(0));
                    break;
            }
        }
        
        return markupElements;
    }
};

Markup.anchor = {
    elementType: "button",
    name: "namfox-anchor-button",
    text: "Anchor",
    
    enabled: function(markupBranch) {
        return markupBranch.anchor;
    },
    
    getMarkup: function(text) {
        return {
            open: "[[#",
            close: "]]"
        };
    }
};

Markup.bold = {
    elementType: "button",
    name: "namfox-bold-button",
    text: "B",
    
    getMarkup: function(text) {
        return {
            open: "[b]",
            close: "[/b]"
        };
    }
};

Markup.code = {
    elementType: "button",
    name: "namfox-code-button",
    text: "Code",
    
    enabled: function(markupBranch) {
        return markupBranch.code;
    },
    
    getMarkup: function(textArea) {
        return {
            open: "[code]",
            close: "[/code]"
        };
    }
};

Markup.customMessages = {
    elementType: "select",
    name: "namfox-custom-messages-select",
    
    change: function(option, textArea) {
        // The first option ("Custom Messages") has a value of 0, which we purposely ignore.
        if (option.value !== 0) {
            Markup.addCustomMessage(option.value, textArea);
        }
        
        // Reset the selected index back to the original.
        option.parentNode.selectedIndex = 0;
    },
    
    enabled: function(markupBranch) {
        return markupBranch.customMessages;
    },
    
    fill: function(select) {
        select.append('<option value="0">Custom Messages</option>');
    
        $.messages.enumerate(function(message) {
            select.append('<option value="' + message.key + '">' + message.name + '</option>');
        });
        
        select.append('<option value="defaultMessage">Default Message</option>');
        
        // Set up an observer for this custom message element, that will update
        // a specific option's innerHTML when a custom message's name is changed.
        var observerService = $("@mozilla.org/observer-service;1").service("nsIObserverService");
        var observer = {
            init: function() {
                observerService.addObserver(this, "custom-message-name-changed", false);
                observerService.addObserver(this, "quit-application", false);
            },
            
            observe: function(subject, topic, data) {
                
                $.verbose("Observe fired : " + topic);
                switch (topic) {
                    case "quit-application":
                        this.unload();
                        break;
                    case "custom-message-name-changed":
                        var customMessage = subject.QueryInterface($("nfxINamedCustomMessage"));
                        
                        var options = select.get(0).options;
                        for (var i = 0; i < options.length; ++i) {
                            var option = options[i];
                            
                            if (option.value === customMessage.key) {
                                $FX(option).html(customMessage.name);
                                break;
                            }
                        }
                        break;
                }
            },
            
            unload: function() {
                observerService.removeObserver(this, "custom-message-name-changed");
                observerService.removeObserver(this, "quit-application");
            }
        };
        
        // We'll use this handler to unload the observer once the user
        // navigates away from this page.
        select.attr("ownerDocument").defaultView.addEventListener(
            "unload",
            function() {
                observer.unload();
            },
            false
        );
        
        observer.init();
    }
};

Markup.horizontalRule = {
    elementType: "button",
    name: "namfox-horizontal-rule-button",
    text: "HR",
    
    enabled: function(markupBranch) {
        return markupBranch.horizontalRule;
    },
    
    getMarkup: function(text) {
        return "[hr]";
    }
};

Markup.indent = {
    elementType: "button",
    name: "namfox-indent-button",
    text: "Indent",
    
    enabled: function(markupBranch) {
        return markupBranch.indent;
    },
    
    getMarkup: function(text) {
        return {
            open: "[ul]",
            close: "[/ul]"
        };
    }
};

Markup.italics = {
    elementType: "button",
    name: "namfox-italics-button",
    text: "I",
    
    getMarkup: function(text) {
        return {
            open: "[i]",
            close: "[/i]"
        };
    }
};

Markup.linkName = {
    _informationProvider: {
        name: function() {
            return prompt("Enter link name:");
        },
        url: function() {
            return prompt("Enter link URL:");
        }
    },
    
    elementType: "button",
    name: "namfox-link-name-button",
    text: "link name=",
    
    click: function(textArea) {
        var text = textArea.value;
        var selectedText = text.substring(textArea.selectionStart, textArea.selectionEnd);
        
        var otherData = "";
        var selectedTextIsUrl = false;
        if (selectedText.match(/http:\/\/\S+/)) {
            otherData = this._informationProvider.name();
            selectedTextIsUrl = true;
        }
        else {
            otherData = this._informationProvider.url();
        }
        
        otherData = otherData || "";
        
        var markup = null;
        if (selectedTextIsUrl) {
            markup = {
                open: "[link name=" + otherData + "]",
                close: "[/link]"
            };
        }
        else {
            markup = {
                open: "[link name=",
                close: "]" + otherData + "[/link]"
            };
        }
        
        Markup.addText(markup, textArea);
    }
};

Markup.php = {
    elementType: "button",
    name: "namfox-php-button",
    text: "PHP",
    
    enabled: function(markupBranch) {
        return markupBranch.php;
    },
    
    getMarkup: function(text) {
        return {
            open: "[php]",
            close: "[/php]"
        };
    }
};

Markup.pre = {
    elementType: "button",
    name: "namfox-pre-button",
    text: "Pre",
    
    enabled: function(markupBranch) {
        return markupBranch.pre;
    },
    
    getMarkup: function(text) {
        return {
            open: "[pre]",
            close: "[/pre]"
        };
    }
};

Markup.privateMessageLink = {
    _informationProvider: {
        recipient: function() {
            return prompt("Enter the PM Recipient");
        },
        subject: function() {
            return prompt("Enter the PM Subject");
        },
        message: function() {
            return prompt("Enter the PM Message");
        }
    },
    elementType: "button",
    name: "namfox-private-message-link-button",
    text: "PM Link",
    
    enabled: function(markupBranch) {
        return markupBranch.privateMessageLink;
    },
    
    click: function(textArea) {
        var recipient = this._informationProvider.recipient();
        
        if (recipient !== null) {
            var subject = this._informationProvider.subject();
            
            if (subject !== null) {
                var message = this._informationProvider.message();
                
                if (message !== null) {
                    var text = $.url.forum("/index.php?fn=send_pm&manual_username=");
                    text += $.urlEncode(recipient);
                    text += "&title=";
                    text += $.urlEncode(subject);
                    text += "&message=";
                    text += $.urlEncode(message);
                    
                    Markup.addText(text, textArea, SelectionRule.MoveCursorToClose);
                }
            }
        }
    }
};

Markup.privateMessageShortHand = {
    elementType: "button",
    name: "namfox-private-message-short-hand-button",
    text: "PM (Short)",
    
    enabled: function(markupBranch) {
        return markupBranch.privateMessageShortHand;
    },
    getMarkup: function(text) {
        return {
            open: "[[pm:",
            close: "]]"
        };
    }
};

Markup.profile = {
    elementType: "button",
    name: "namfox-profile-button",
    text: "Profile/Member",
    
    enabled: function(markupBranch) {
        return markupBranch.profile;
    },
    
    getMarkup: function(text) {
        return {
            open: "[[member:",
            close: "]]"
        };
    }
};

Markup.quote = {
    _informationProvider: {
        originator: function() {
            return prompt("Originator?");
        }
    },
    elementType: "button",
    name: "namfox-quote-button",
    text: "Quote",
    
    click: function(textArea) {
        var originator = this._informationProvider.originator();
        
        if (originator) {
            Markup.addText({ open: "[q=" + originator + "]", close: "[/q]" }, textArea);
        }
        else {
            Markup.addText({ open: "[q]", close: "[/q]" }, textArea);
        }
    }
};

Markup.replace = {
    _informationProvider: {
        oldText: function() {
            return prompt("Replace what?");
        },
        newText: function() {
            return prompt("With what?");
        }
    },
    elementType: "button",
    name: "namfox-replace-button",
    text: "Replace",
    
    enabled: function(markupBranch) {
        return markupBranch.replace;
    },
    
    click: function(textArea) {
        var oldText = this._informationProvider.oldText();
        var newText = this._informationProvider.newText();
        
        textArea.value = textArea.value.replace($.toRegExp(oldText, "g"), newText);
    }
};

Markup.strike = {
    elementType: "button",
    name: "namfox-strike-button",
    text: "Strike",
    
    enabled: function(markupBranch) {
        return markupBranch.strike;
    },
    
    getMarkup: function(text) {
        return {
            open: "[strike]",
            close: "[/strike]"
        };
    }
};

Markup.subscript = {
    elementType: "button",
    name: "namfox-subscript-button",
    text: "Sub",
    
    enabled: function(markupBranch) {
        return markupBranch.subscript;
    },
    
    getMarkup: function(text) {
        return {
            open: "[sub]",
            close: "[/sub]"
        };
    }
};

Markup.superscript = {
    elementType: "button",
    name: "namfox-superscript-button",
    text: "Sup",
    
    enabled: function(markupBranch) {
        return markupBranch.superscript;
    },
    
    getMarkup: function(text) {
        return {
            open: "[sup]",
            close: "[/sup]"
        };
    }
};

Markup.underline = {
    elementType: "button",
    name: "namfox-underline-button",
    text: "U",
    
    getMarkup: function(text) {
        return {
            open: "[u]",
            close: "[/u]"
        };
    }
};

Markup.videos = {
    elementType: "select",
    name: "namfox-videos-select",
    
    _specs: {
        nv: {
            open: "[neovid]",
            close: "[/neovid]"
        },
        gs: {
            open: "[gamespot]",
            close: "[/gamespot]",
            transformer: $.transformers.gamespot
        },
        gt: {
            open: "[gametrailers]",
            close: "[/gametrailers]",
            transformer: $.transformers.gametrailers
        },
        gtu: {
            open: "[gametrailers_user]",
            close: "[/gametrailers_user]",
            transformer: $.transformers.gametrailersUser
        },
        gv: {
            open: "[gamevideos]",
            close: "[/gamevideos]",
            transformer: $.transformers.gamevideos
        },
        ign: {
            open: "[ignvideo]",
            close: "[/ignvideo]"
        },
        yt: {
            open: "[yt]",
            close: "[/yt]"
        }
    },
    
    change: function(option, textArea) {
        // The first option ("Videos") has a value of -1, which we purposely ignore.
        if (option.value !== -1) {
            
            var spec = this._specs[option.value];
            
            if (spec) {
                Markup.addText(spec, textArea, SelectionRule.MoveCursorToClose, spec.transformer);
            }
        }
        
        // Reset the selected index back to the original.
        option.parentNode.selectedIndex = 0;
    },
    
    enabled: function(markupBranch) {
        return markupBranch.videos;
    },
    
    fill: function(select) {
        select.append('<option value="-1">Videos</option>');
        select.append('<option value="nv">Neovid</option>');
        select.append('<option value="gs">Gamespot</option>');
        select.append('<option value="gt">Gametrailers</option>');
        select.append('<option value="gtu">Gametrailers User</option>');
        select.append('<option value="gv">Gamevideos</option>');
        select.append('<option value="ign">ignvideo</option>');
        select.append('<option value="yt">Youtube</option>');
    }
};

Markup.all = [
    Markup.anchor,
    Markup.bold,
    Markup.code,
    Markup.customMessages,
    Markup.horizontalRule,
    Markup.indent,
    Markup.italics,
    Markup.linkName,
    Markup.php,
    Markup.pre,
    Markup.privateMessageLink,
    Markup.privateMessageShortHand,
    Markup.profile,
    Markup.quote,
    Markup.replace,
    Markup.strike,
    Markup.subscript,
    Markup.superscript,
    Markup.underline,
    Markup.videos
];

}});